#!/usr/bin/env python
# -*- coding: utf-8 -*-
# We require the srvrmgrdIO module to prepare the request and talk to servermgrd
# Also require python plistlib library (apt-get install python-plistlib [in Debian/Ubuntu])
# http://svn.python.org/projects/python/trunk/Lib/plistlib.py
#
#
#    Copyright (C) 2010 - 2012 Felim Whiteley
#
#    Author: Felim Whiteley <felimwhiteley@gmail.com>
#             http://www.linkedin.com/in/felimwhiteley
#	          http://www.whiteleytech.ie
#
#
#    This is free software; you can redistribute it and/or modify it
#    under the terms of the GNU Lesser General Public License as
#    published by the Free Software Foundation; version 3 of
#    the License, or any later version.
#
#    This software is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this software; if not, write to the Free
#    Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
#    02110-1301 USA, or see the FSF site: http://www.fsf.org.


import pickle, srvrmgrdIO, subprocess, sys, time

__version__ = "0.6.5"

configINI = '/etc/libServerManagerDaemon.ini'

# Default to NOT using datafile in /tmp for shareable data;
# If set config file then check set value for dataFileMode
dataFileMode = 0
# Default To Hiding SubService Results When Ignored
showDisabledSubServices = 0
# Output PerfData
displayPerfData = 1
# Performance Data
# 'label'=value[UOM];[warn];[crit];[min];[max]
perfData = ""
# Display a master "RUNNING" for services with sub-services
masterState = 0
# Bytes usually stored as 1024, can be 1000 in metric byte values
byteBase = 1024

# Debug Logging
def logMessage(serverMessage, serverAddress, serverPort):
    try:
        logFileLocation = "/tmp/%s_%s.debug" % (serverAddress, serverPort)
        logFile = open(logFileLocation, "a")
        #print "%s: %s" % (time.strftime("%Y-%m-%d %H:%M:%S"), serverMessage)
        logFile.write("%s: %s\n" % (time.strftime("%Y-%m-%d %H:%M:%S"), serverMessage))
        logFile.flush()
        logFile.close()
    except:
        print "DEBUG_LOG_ERROR: %s" % (sys.exc_info()[1])

# CLI Debug Switch
debugMode = 0
testMode = 0
listModules = 0
listModulesCommands = 0
# Debug & Testing Modes
if len(sys.argv) > 1:
    if sys.argv[1] == "DEBUG":
        debugMode = 1
    elif sys.argv[1] == "TEST":
        testMode = 1
        debugMode = 1
    elif sys.argv[1] in ["LIST", "LISTDETAILS"]:
        listModules = 1
        if sys.argv[1] == "LISTDETAILS":
            listModulesCommands = 1

# Module Configuration
osxServices = {
    'addressbook' : ['requestsPerHour'],
    'afp': ['currentConnections'],
    'backup': [],
    'calendar': [],
    'dhcp': ['numDHCPLeases', 'numConfiguredStaticMaps', 'timeOfSnapShot', 'numActiveStaticMaps'],
    'dirserv': ['timState', 'ldapdState', 'kdcStatus', 'lookupdState', 'passwordServiceState', 'netinfodState', 'netinfodParentState'],
    'dns': ['zonesAllocated', 'SOAQueriesInProgress', 'transfersRunning', 'transfersDeferred'],
    'ftp': ['realConnectionCount', 'anonymousAccessPermitted', 'anonymousConnectionCount'],
    'ipfilter': [],
    'jabber': ['serviceMode', 'jabberdState', 'mucState', 'proxyState'],
    'mysql': ['currentConnections', 'currentThroughput'],
    'nat': ['activeICMP', 'activeTCP', 'activeUDP'],
    'netboot': [],
    'nfs': ['nsfd', 'mountd', 'portmap', 'rpc.lockd'],
    'pcast': ['onlineCameras', 'totalCameras', 'currentJobs', 'totalJobs'],
    'print': ['currentJobs', 'currentQueues'],
    'proxy': [],
    'qtss': ['stateAFP', 'stateDHCP', 'stateHTTP', 'stateNFS', 'stateTFTP'],
    'radius': [],
    'sharing': [],
    'signaler': [],
    'smb': ['currentConnections'],
    'swupdate': ['numOfMirroredPkg', 'numOfEnbabledPkg'],
    'teams': [],
    'vpn': [],
    'web': ['apacheState', 'proxyState', 'totalKBytes', 'totalRequests'],
    'webobjects': [],
    'xgrid': {'agentStatus': ['status', 'activeCPUPower'], 'controllerStatus': ['status', 'workingCPUPower']}
}

def convertListToCsv(pythonList, sqlMode=0):
    csvString = ''
    for listItem in pythonList:
        if csvString == '':
            if sqlMode:
                csvString = "'%s'" % listItem
            else:
                csvString = "%s" % listItem
        else:
            if sqlMode:
                csvString = "%s,'%s'" % (csvString, listItem)
            else:
                csvString = "%s,%s" % (csvString, listItem)
    return csvString


def pluginUsage():
    print "OS X Server Monitoring Tool\n\tVersion: %s" % (__version__)
    print "\tCopyright (C) 2010 - 2012 Felim Whiteley"
    print "\thttp://www.whiteleytech.ie"
    print "\thttp://code.google.com/p/libsrvrmgrd-osx/"
    print "This software is licensed under the GNU Lesser General Public License\n"
    print "USAGE: check_osx_server SERVICENAME ADDRESS PORT USER PASSWORD [Optional Sub-Service Ignore List]"
    print ""
    print "\t--------\t------------"
    print "\tServices\tSub-Services"
    print "\t--------\t------------"
    for serviceName, subServiceList in osxServices.iteritems():
        if len(serviceName) > 7:
            txtTabs = "\t"
        else:
            txtTabs = "\t\t"
        if isinstance(subServiceList, dict):
            print "\t%s%s%s" % (serviceName, txtTabs, convertListToCsv(subServiceList.keys()))
        elif len(subServiceList) == 0:
            noSubServices = "-" #"--NO SUB-SERVICES AVAILABLE--"
            print "\t%s%s%s" % (serviceName, txtTabs, noSubServices)
        else:
            print "\t%s%s%s" % (serviceName, txtTabs, convertListToCsv(subServiceList))
    print "\tsoftwareupdates\t-" #"-NO SUB-SERVICES AVAILABLE--"
    print "\txserveraid\t-" #"-NO SUB-SERVICES AVAILABLE--"
    print "\nWhen adding Sub-Service ignore values they should be the last argument, seperated by commas and contain no whitespace"
    print "\nEXAMPLE: check_osx_server jabber 172.16.12.1 311 admin_user admin_user_password mucState,proxyState\n"
    print "\tmucState & proxyState will be ignored and will not trigger a warning"
    print ""
    print "-----------"
    print "System Load"
    print "-----------"
    print "USAGE: check_osx_server SYSTEMSTAT ADDRESS PORT USER PASSWORD WARN_THRESHOLD CRIT_THRESHOLD"
    print "\n\tThe WARN_THRESHOLD & CRIT_THRESHOLD are optional and default to 80% & 90% respectivly"
    print "\t(If you do use your own values you must use both or it will trigger an error)"
    print "\tYou can also specify values in K, M, G, T & P values, either in combination with"
    print "\tpercentage or in comma seperated list where you might have hard limits for certain drives"
    print "\nSYSTEMSTAT:"
    print "\tcpuUsage\t\tView Percentage Of Aggregated CPU(s) & Cores Used"
    print "\tnetUsage\t\tView The Overall Kilobits/Second Network Usage"
    print "\thddUsage\t\tView Percentage Of HDD Used On All Drives"
    print "\tsysUsage\t\tCombined View Of All Of The Above"
    print "\t\tExample:"
    print """\t\t\t./check_osx_server hddUsage 192.168.1.10 311 admin_user admin_password"""
    print """\t\t\t./check_osx_server hddUsage 192.168.1.10 311 admin_user admin_password 87 92"""
    print ""
    print "\thddUsageDrive\t\tView Percentage Of HDD Used On A Specific Drive"
    print "\t\tExample:"
    print """\t\t\t./check_osx_server hddUsageDrive 192.168.1.10 311 admin_user admin_password "Promise RAID" 80,100G 90,200G"""
    print "\t\tNote:"
    print """\t\t\tThe drive name should be wrapped in " " if there is whitespace in the name as the above example"""
    print ""
    print "------------------"
    print "Informational Mode"
    print "------------------"
    print "USAGE: check_osx_server INFOMODE ADDRESS PORT USER PASSWORD"
    print "\nINFOMODES:"
    print "\tafpUserList\t\tView User(s) Connected Via AFP"
    print "\tsmbUserList\t\tView User(s) Connected Via SMB"
    print "\tserverRole\t\tList The Roles & Services Configured"
    print "\tsoftwareupdatesList\tList All Updates Required By The Server"
    print ""
    print "--------"
    print "Dev Mode"
    print "--------"
    print "USAGE: check_osx_server DEVMODE ADDRESS PORT USER PASSWORD"
    print "\nDEVMODES:"
    print "\tLIST\t\tView The Modules We Can Probe And Highlight Which Are Active On The Host"
    print "\tLISTDETAILS\tAlso Shows The SubServices Per Module"
    print ""
    sys.exit(0)

# Load Config File If Available
configDebugMessage = ''
try:
    import ConfigParser
    config = ConfigParser.ConfigParser()
    config.readfp(open(configINI))
    try:
        dataFileMode = int(config.get("config", "dataFileMode"))
    except:
        pass
    try:
        showDisabledSubServices = int(config.get("config", "showDisabledSubServices"))
    except:
        pass
    try:
        displayPerfData = int(config.get("config", "displayPerfData"))
    except:
        pass
    try:
        masterState = int(config.get("config", "masterState"))
    except:
        pass
    try:
        byteBase = int(config.get("config", "byteBase"))
    except:
        pass
    configDebugMessage = "DEBUG: %s Found: dataFileMode:%s" % (configINI, dataFileMode)
except:
    configDebugMessage = "ERROR: %s Not Found Or Invalid" % (configINI)

if listModules:
    if len(sys.argv) == 6:
        serverAddress = sys.argv[2]
        serverPort = sys.argv[3]
        serverUser = sys.argv[4]
        serverPassword = sys.argv[5]
        moduleList = []
        httpError, srvrmgrData = srvrmgrdIO.requestServerData("https://%s:%s" % (serverAddress, serverPort), serverUser, serverPassword)
        if not httpError:
            for item in srvrmgrData.split('\n'):
                if 'HREF="servermgr' in item:
                    rawModule = item[item.index('HREF="servermgr'):]
                    try:
                        moduleName = rawModule.split('"')[1].split(".")[0]
                        moduleList.append(moduleName)
                    except:
                        print "PARSE_ERROR: %s" % (item)
                # This can show us the other data in the file we aren't interested in
                #else:
                #	print "PARSE_MISS: %s" % item
            if len(moduleList) > 0:
                print " Modules Found:\n [* = monitoring available]"
            osxServicesAvail = osxServices.keys()
            # Clunkyily adding a few we monitor in non-dict mode
            # TODO - Tidy this up
            osxServicesAvail.append("mail")
            for srvrModule in moduleList:
                if srvrModule.replace("servermgr_", "") in osxServicesAvail:
                    print "\t\t* %s" % (srvrModule)
                else:
                    print "\t\t  %s" % (srvrModule)
                if listModulesCommands:
                    httpError, srvrmgrData = srvrmgrdIO.requestServerData("https://%s:%s/%s.html" % (serverAddress, serverPort, srvrModule), serverUser, serverPassword)
                    if not httpError:
                        for item in srvrmgrData.split('\n'):
                            if 'OPTION VALUE="' in item:
                                rawModule = item[item.index('OPTION VALUE="'):]
                                try:
                                    # Example Data:
                                    # <OPTION VALUE="authenticateAdmin">authenticateAdmin
                                    commandName = rawModule.split('"')[1]
                                    print "\t\t\t%s" % (commandName)
                                except:
                                    pass
                                    #print "\t\t\tERROR:%s" % (sys.exc_info()[0])
                    #else:
                    #	print "\t\t\tERROR:%s" % (srvrmgrData)
        else:
            print "ERROR: Problem Contacting Server:%s" % (srvrmgrData)
        sys.exit(0)
    else:
        pluginUsage()
elif len(sys.argv) >= 6:
    # If We Are Running In Debug Then Shift The Position Of All Variables
    checkService = sys.argv[1 + debugMode]
    serverAddress = sys.argv[2 + debugMode]
    serverPort = sys.argv[3 + debugMode]
    serverUser = sys.argv[4 + debugMode]
    serverPassword = sys.argv[5 + debugMode]
    # Ignore List For subServices
    if len(sys.argv) > (6 + debugMode):
        subServiceIgnoreCSV = sys.argv[6 + debugMode]
        subServiceIgnore = subServiceIgnoreCSV.split(',')
    else:
        subServiceIgnore = []
    if debugMode:
        logMessage("++++++++++++++++ START ++++++++++++++++++\n", serverAddress, serverPort)
        if configDebugMessage:
            # Send Config Parse Message to Log
            logMessage(configDebugMessage, serverAddress, serverPort)
        try:
            # Send System Args To Log
            logMessage(sys.argv, serverAddress, serverPort)
            # Send List Of Files Belonging To This Server
            tempDirList = subprocess.Popen('ls -l /tmp/%s*' % (serverAddress), shell=True, stdout=subprocess.PIPE).stdout
            tempDirListing = tempDirList.readlines()
            tempData = ''
            for fileFound in tempDirListing:
                tempData += fileFound
            tempData = "Files In /tmp\n%s" % (tempData)
            logMessage(tempData, serverAddress, serverPort)
        except:
            print "DEBUG_ERROR: %s" % (sys.exc_info()[1])
else:
    pluginUsage()


warningCount = 0
criticalCount = 0
unknownCount = 0


def getServiceData(servermgrdModule, dataReqCommand, dataReqVariant=None, dataReqTimescale=None, dataReqIdentifier=None, dataReqOffset=None, dataReqAmount=None):
    # TEST Mode - This will be moving somewhere else
    if testMode:
        if servermgrdModule == "servermgr_xgrid":
            # XGrid test Data
            serviceData = {'servicePortsAreRestricted': 'NO', 'servicePortsRestrictionInfo': [], 'state': 'RUNNING', 'readWriteSettingsVersion': 1, 'startedTime': '', 'setStateVersion': 1, 'command': 'getState', 'controllerStatus': {'status': 'OK', 'workingCPUPower': '5'}, 'logPaths': {'xgridSystemLog': '/var/log/system.log', 'xgridAgentLog': '/Library/Logs/Xgrid/xgridagentd.log', 'xgridControllerLog': '/Library/Logs/Xgrid/xgridcontrollerd.log'}, 'agentStatus': {'status': 'STOPPED', 'activeCPUPower': '1'}}
            return serviceData
        elif servermgrdModule == "servermgr_info" and dataReqCommand == "getHardwareInfo":
            # hddStatus, cpuUsage etc.
            serviceData = {'cpuUsageBy100': 315, 'volumeInfosArray': [{'totalBytes': 161061273600, 'name': 'ServerHD', 'freeBytes': 146084642816}, {'totalBytes': 161061273600, 'name': 'Net Boot HD', 'freeBytes': 107296305152}, {'totalBytes': 1177566953472, 'name': 'SnapshotHD', 'freeBytes': 472142860288}, {'totalBytes': 2450234761216, 'name': 'BackupHD', 'freeBytes': 386296975360}, {'totalBytes': 549411840000, 'name': 'DataHD', 'freeBytes': 157978660864}], 'networkThroughputBy1K': 655, 'cpuUsagesBy100Array': [{'cpuUsage-0': 903}, {'cpuUsage-1': 13}, {'cpuUsage-2': 682}, {'cpuUsage-3': 11}, {'cpuUsage-4': 550}, {'cpuUsage-5': 10}, {'cpuUsage-6': 343}, {'cpuUsage-7': 6}], 'command': 'getHardwareInfo', 'networkThroughput': 671107}
            return serviceData
        else:
            print "TEST NOT AVAILABLE: '%s'" % servermgrdModule
            sys.exit(0)
    # buildXML ( command, variant=None, timescale=None, identifier=None, offset=None, amount=None )
    dataRequest = srvrmgrdIO.buildXML(dataReqCommand, dataReqVariant, dataReqTimescale, dataReqIdentifier, dataReqOffset, dataReqAmount)
    if debugMode:
        serverMessage = "#### REQUEST ####\n%s\n #### END REQUEST ####\n" % (dataRequest)
        logMessage(serverMessage, serverAddress, serverPort)
        serverMessage = "VARIABLES: [%s]\n servermgrdModule = '%s'\n serverAddress = '%s'\n serverPort  = '%s'\n serverUser = '%s'\n serverPassword = '%s'" % (len(sys.argv), servermgrdModule, serverAddress, serverPort, serverUser, serverPassword)
        logMessage(serverMessage, serverAddress, serverPort)
    if dataFileMode:
        # Store Data In /tmp
        dataFileLocation = srvrmgrdIO.buildDataFile(servermgrdModule, dataRequest, serverAddress, serverPort, serverUser, serverPassword, debugMode)
        dataFile = open(dataFileLocation, "rb")
        serviceData = pickle.load(dataFile)
        dataFile.close()
    else:
        # Dont Store Data In /tmp
        serviceData = srvrmgrdIO.sendXML(servermgrdModule, dataRequest, serverAddress, serverPort, serverUser, serverPassword)
    return serviceData


def getServiceState(serviceData):
    # Set some intial values
    serviceStatus = ""
    criticalCount = 0
    warningCount = 0
    unknownCount = 0
    if 'state' in serviceData:
        serviceState = serviceData['state']
        if serviceState in ['RUNNING', 'idle']:
            serviceStatus = "%s" % (serviceState)
        elif serviceState == 'UNKNOWN':
            unknownCount += 1
        else :
                criticalCount += 1
        serviceStatus = "%s" % (serviceState)
    else:
        serviceStatus = "UNKNOWN: Invalid Data Returned From Service Check"
        unknownCount += 1
    return serviceStatus, criticalCount, warningCount, unknownCount


def getServiceThreshold(sensorReading, warnLevel=80, critLevel=90):
    # Convert to INT : We may have had comma seperated list of 2 thresholds
    # Debug Output
    if debugMode:
        serverMessage = "#### getServiceThreshold####\nsensorReading: %s\nwarnLevel: %s\ncritLevel: %s" % (sensorReading, warnLevel, critLevel)
        logMessage(serverMessage, serverAddress, serverPort)
    warningCount = 0
    criticalCount = 0
    if warnLevel < critLevel:
        # For Processor/Mem Usage Where We Have An Upper Limit
        if sensorReading >= critLevel:
            criticalCount += 1
        elif sensorReading >= warnLevel:
            warningCount += 1
    elif warnLevel > critLevel:
        # Where We Want To Be Warned When They Run Out Of Resouce ie. Lower Limit
        if sensorReading <= critLevel:
            criticalCount += 1
        elif sensorReading <= warnLevel:
            warningCount += 1
    else:
        # Stop Folk Setting Warn & Crit To Same Value... ;)
        print "ERROR: Warning & Critical Levels Are The Same"
        sys.exit(1)
    return warningCount, criticalCount


def getIndent(lineOfText):
    whiteSpace = 0
    for char in lineOfText:
        if char == ' ':
            whiteSpace += 1
        else:
            break
    return whiteSpace


def getSubNode(i, raidData, keyIndent, firstIndent):
    raidStatus = {}
    nextKeyIndent = firstIndent
    while nextKeyIndent >= firstIndent:
        i += 1
        currentKeyIndent = getIndent(raidData[i])
        nextKeyIndent = getIndent(raidData[i+1])
        itemData = raidData[i].lstrip().rstrip().split(':')
        if itemData[1] == '':
            i, raidStatus[itemData[0]] = getSubNode(i, raidData, currentKeyIndent, nextKeyIndent)
            currentKeyIndent = getIndent(raidData[i])
            nextKeyIndent = getIndent(raidData[i+1])
        else:
            raidStatus[itemData[0]] = itemData[1].lstrip().rstrip()
    return i, raidStatus


def xraidStatus(profileData):
    i = 0
    profileData = profileData.split('\n')
    # Strip blank items...
    raidData = [item for item in profileData if item != '']
    raidStatus = {}
    # Keywords
    raidKeys = ['Battery Info', 'Drives', 'RAID Sets', 'Volumes']
    # Parse Card Info First
    while i < len(raidData):
        if raidData[i] == 'Hardware RAID:':
            i += 1
            raidCardDetected = 1
            while i < len(raidData):
                if raidData[i][0] == ' ':
                    raidKey = raidData[i].lstrip().rstrip(':')
                    if raidKey in raidKeys:
                        # Get The Section Indent
                        keyIndent = getIndent(raidData[i]) # 6 or 8
                        firstIndent = getIndent(raidData[i+1]) # 10 or 12
                        i, raidStatus[raidKey] = getSubNode(i, raidData, keyIndent, firstIndent)
                    else:
                        i+= 1
                else:
                    # If the line starts with a non-whiespace then we have ended the Raid block
                    break
            # Exit as we've processed the raid card by now
            break
        i+= 1
    return raidStatus


def getSubServiceState(warningCount, subServices, serviceData, subServiceIgnore):
    subServiceStatus = ""
    # Some Services Use Sub-Arrays Within The PList - We need to handle a Sub-SubService
    # Effectivly: This is rather ugly and should really change this!
    if isinstance(subServices, dict):
        for subServiceName, subServiceArray in subServices.iteritems():
            if subServiceName in serviceData:
                subServiceInfo = ''
                for subServiceItem in subServiceArray:
                    # Default to Showing Sub Services In Output
                    displaySubService = 1
                    if subServiceItem in serviceData[subServiceName].keys():
                        subServiceState = serviceData[subServiceName][subServiceItem]
                        # Are we tracking Sub Service Warnings For This Sub Service
                        if subServiceName not in subServiceIgnore:
                            # Need to strip out possible float values - This is very hackish
                            if not str(subServiceState).replace('.', '').isdigit():
                                if subServiceState.upper() in ('UNAVAILABLE', 'STOPPED', 'OFF'):
                                    warningCount += 1
                        else:
                            # A Sub Service We Want To Ignore
                            if not showDisabledSubServices:
                                # Are We Globally Showing Disabled Subservices
                                displaySubService = 0
                        if displaySubService:
                            if subServiceInfo == '':
                                subServiceInfo = "[%s%s:%s " % (subServiceInfo, subServiceItem, serviceData[subServiceName][subServiceItem])
                            else:
                                subServiceInfo = "%s%s:%s " % (subServiceInfo, subServiceItem, serviceData[subServiceName][subServiceItem])
                if subServiceInfo != '':
                    subServiceStatus = "%s%s:%s] " % (subServiceStatus, subServiceName, subServiceInfo.rstrip())
    # Normal Sub-Services
    else:
        for subService in subServices:
            # Default to Showing Sub Services In Output
            displaySubService = 1
            if subService in serviceData:
                subServiceState = serviceData[subService]
                if subService not in subServiceIgnore:
                    # Need to strip out possible float values - This is very hackish
                    if not str(subServiceState).replace('.', '').isdigit():
                        if subServiceState.upper() in ('STOPPED', 'OFF'):
                            warningCount += 1
                else:
                    # Ignoring SubServices
                    # Are We Showing Them?
                    if not showDisabledSubServices:
                        displaySubService = 0
                if displaySubService:
                    subServiceStatus = "%s%s:%s " % (subServiceStatus, subService, subServiceState)
    return subServiceStatus, warningCount


def convertToBytes(humanisedBytes):
    humanisedBytes = str(humanisedBytes)
    if humanisedBytes[-1].isdigit():
        return int(humanisedBytes)
    else:
        if humanisedBytes[-1] == "B":
            return int(humanisedBytes[:-1])
        elif humanisedBytes[-1] == "K":
            return int(humanisedBytes[:-1])*byteBase
        elif humanisedBytes[-1] == "M":
            return int(humanisedBytes[:-1])*(byteBase**2)
        elif humanisedBytes[-1] == "G":
            return int(humanisedBytes[:-1])*(byteBase**3)
        elif humanisedBytes[-1] == "T":
            return int(humanisedBytes[:-1])*(byteBase**4)
        elif humanisedBytes[-1] == "P":
            return int(humanisedBytes[:-1])*(byteBase**5)


def humaniseBytes(numberInBytes):
    numberInBytes = str(numberInBytes)
    if len(numberInBytes) > 15:
        humanisedNumber = int(int(numberInBytes)/(byteBase**5))
        humanisedNumber = "%sPB" % (humanisedNumber)
    if len(numberInBytes) > 12:
        humanisedNumber = int(int(numberInBytes)/(byteBase**4))
        humanisedNumber = "%sTB" % (humanisedNumber)
    elif len(numberInBytes) > 9:
        humanisedNumber = int(int(numberInBytes)/(byteBase**3))
        humanisedNumber = "%sGB" % (humanisedNumber)
    elif len(numberInBytes) > 6:
        humanisedNumber = int(int(numberInBytes)/(byteBase**2))
        humanisedNumber = "%sMB" % (humanisedNumber)
    elif len(numberInBytes) > 3:
        humanisedNumber = int(int(numberInBytes)/(byteBase))
        humanisedNumber = "%skB" % (humanisedNumber)
    else:
        humanisedNumber = "%sB" % (numberInBytes)
    return humanisedNumber


# Set a Default State - Stops bug at end if we never set it... - UGLY!
serviceStatus = ""
# Process The Service
if checkService in ('afpUserList', 'smbUserList'):
    dataReqCommand = 'getConnectedUsers'
    # Strip First 3 Characters To Run Against Correct OSX Module
    serviceData = getServiceData('servermgr_%s' % (checkService[:3]), dataReqCommand, 'withDetails')
    serviceStatus, criticalCount, warningCount, unknownCount = getServiceState(serviceData)
    if not criticalCount:
        userCount = 0
        userList = "%s Connections Active\n" % (checkService[:3].upper())
        for userConnection in serviceData['usersArray'] :
            userCount += 1
            userList = "%s\t%s:\t%s\n" % (userList, userConnection['ipAddress'], userConnection['name'])
        if userCount:
            print userList
        serviceStatus = "Connections:%s" % (userCount)
elif checkService in ('sysUsage', 'cpuUsage', 'hddUsage', 'hddUsageDrive', 'netUsage'):
    dataReqCommand = 'getHardwareInfo'
    serviceData = getServiceData('servermgr_info', dataReqCommand, '')
    serviceStatus = ''
    warnLevel = None
    critLevel = None
    warningCountMaster = 0
    criticalCountMaster = 0
    # It's optional to provide the thresholds but must provide both if you do
    # Add new offset when we have perDisk requests
    perDiskName = ''
    if checkService == "hddUsageDrive":
        perDiskOffset = 1
        # Drive Name With Space In it wrapped In double quotes
        if sys.argv[6 + debugMode][0] == '"':
            i = 6 + debugMode
            perDiskName = sys.argv[i]
            while i < len(sys.argv):
                if sys.argv[i][-1] == '"':
                    break
                else:
                    # We use more than one argv for the name so need to pass the arg count check
                    perDiskOffset += 1
                    i += 1
                    try:
                        perDiskName += "%s %s" % (perDiskName, sys.argv[i])
                    except:
                        print "ERROR: Non-Matching Quotes On DriveName"
                        sys.exit(1)
            perDiskName = perDiskName.lstrip('"').rstrip('"')
        else:
            perDiskName = sys.argv[6 + debugMode]
    else:
        perDiskOffset = 0
    if len(sys.argv) > (6 + debugMode + perDiskOffset):
        # Must Have Both Warn And Crit Set
        if len(sys.argv) == (8 + debugMode + perDiskOffset):
            warnLevel = sys.argv[6 + debugMode + perDiskOffset]
            critLevel = sys.argv[7 + debugMode + perDiskOffset]
        else:
            print "ERROR: Incorrect Number Of Arguments"
            sys.exit(1)
    if checkService in ('sysUsage', 'cpuUsage'):
        sensorReading = int(serviceData['cpuUsageBy100']/100)
        if (warnLevel and critLevel) and checkService != 'sysUsage':
            warningCount, criticalCount = getServiceThreshold(sensorReading, warnLevel, critLevel)
        elif checkService != 'sysUsage':
            warningCount, criticalCount = getServiceThreshold(sensorReading)
        warningCountMaster += warningCount
        criticalCountMaster += criticalCount
        serviceStatus += "cpuUsage:%s%% " % (sensorReading)
        perfData += "cpuUsage=%s;;;;; " % (sensorReading)
    if checkService in ('sysUsage', 'netUsage'):
        sensorReading = int(serviceData['networkThroughputBy1K'])
        # If no threshold set then we don't check becasue no precentage calculation possible with network
        if (warnLevel and critLevel) and checkService != 'sysUsage':
            warningCount, criticalCount = getServiceThreshold(sensorReading, warnLevel, critLevel)
        warningCountMaster += warningCount
        criticalCountMaster += criticalCount
        serviceStatus += "netUsage:%skb/s " % (sensorReading)
        perfData += "netUsage=%s;;;;; " % (sensorReading)
    if checkService in ('sysUsage', 'hddUsage', 'hddUsageDrive'):
        hddStatus = ""
        for hddInfo in serviceData['volumeInfosArray']:
            hddName = hddInfo['name']
            # When running in 1-Disk only mode
            if checkService == 'hddUsageDrive':
                if debugMode:
                    logMessage("perDiskName='%s' hddName='%s'" % (perDiskName, hddName), serverAddress, serverPort)
                if perDiskName != hddName:
                    continue
            usedBytes = hddInfo['totalBytes'] - hddInfo['freeBytes']
            perfData += "totalBytes.%s=%s;;;;; freeBytes.%s=%s;;;;; usedBytes.%s=%s;;;;; " % (hddName.replace('"', ''), hddInfo['totalBytes'], hddName.replace('"', ''), hddInfo['freeBytes'], hddName.replace('"', ''), usedBytes)
            hddUsagePercentage = int((float(usedBytes)/float(hddInfo['totalBytes'])) * 100)
            # If Thresholds Are Set else use defaults of 80 & 90
            hddStatus += "'%s':%s%% " % (hddName, hddUsagePercentage)
            if (warnLevel and critLevel) and checkService != 'sysUsage':
                # Check for Thresholds set with 80,123123123 format
                # ie. percentage,bytes
                # Must have both warn & crit in non-percent mode otherwise be comparing
                # a warn of % and crit of non-% which would be very messy
                if len(warnLevel.split(',')) > 1 and len(critLevel.split(',')) > 1:
                    warningCount, criticalCount = getServiceThreshold(usedBytes, convertToBytes(warnLevel.split(',')[1]), convertToBytes(critLevel.split(',')[1]))
                    warningCountMaster += warningCount
                    criticalCountMaster += criticalCount
                # Check if a non-percent only threshold
                if not warnLevel.split(',')[0][-1].isdigit() and not critLevel.split(',')[0][-1].isdigit():
                    warningCount, criticalCount = getServiceThreshold(usedBytes, convertToBytes(warnLevel.split(',')[0]), convertToBytes(critLevel.split(',')[0]))
                else:
                    warningCount, criticalCount = getServiceThreshold(hddUsagePercentage, warnLevel.split(',')[0], critLevel.split(',')[0])
                warningCountMaster += warningCount
                criticalCountMaster += criticalCount
            elif checkService != 'sysUsage':
                warningCount, criticalCount = getServiceThreshold(hddUsagePercentage)
                warningCountMaster += warningCount
                criticalCountMaster += criticalCount
        if checkService == 'hddUsageDrive':
            serviceStatus = "%s %s" % (hddStatus.rstrip(), humaniseBytes(usedBytes))
        else:
            serviceStatus += "hddUsage:[%s] " % (hddStatus.rstrip())
    warningCount = warningCountMaster
    criticalCount = criticalCountMaster
elif checkService == "mail":
    serviceData = getServiceData('servermgr_mail', 'getState', 'withDetails')
    serviceStatus, criticalCount, warningCount, unknownCount = getServiceState(serviceData)
    if not criticalCount:
        serviceStatus = "%s " % (serviceStatus) # Keep Initial RUNNING
        for protocol in serviceData['protocolsArray']:
            name = protocol['protocol']
            kind = protocol['kind']
            # TODO - Seems to be gone now...
            #total = protocol['total']
            #active = protocol['active']
            status = protocol['status']
            # TODO - Taken this out for now but will plit it out into perfdata
            #if status == 'ON' :
            #	serviceStatus = "%s%s-%s:Act(%s)Total(%s) " % (serviceStatus, name, kind, active, total)
            #else :
            #	serviceStatus = "%s%s-%s:(%s) " % (serviceStatus, name, kind, status)
            # TODO - Use the main subService Check code
            subServiceName = "%s-%s" % (name, kind[:3])
            if status == 'OFF' and subServiceName not in subServiceIgnore:
                warningCount += 1
            serviceStatus = "%s%s:%s " % (serviceStatus, subServiceName, status)
elif checkService in ("softwareupdates", "softwareupdatesList"):
    patchList = []
    normalPatches = 0
    recommendedPatches = 0
    restartRequired = 0
    serviceData = getServiceData('servermgr_info', 'getSWUpdateStatus')
    if 'swupdateStatus' not in serviceData:
        # We have not updated our list from Apple servers yet so need to start a scan
        serviceData = getServiceData('servermgr_info', 'startSWUpdateScan')
        serviceData = getServiceData('servermgr_info', 'getSWUpdateStatus')
    if 'swupdateStatus' in serviceData:
        if 'phaseResultsArray' in serviceData['swupdateStatus']:
            for softwareUpdate in serviceData['swupdateStatus']['phaseResultsArray']:
                if softwareUpdate['ignored']: # ie. Ignored Is True - Meaning Don't Ignore!
                    normalPatches += 1
                    warningCount += 1
                else:
                    recommendedPatches += 1
                    criticalCount += 1
                if softwareUpdate['restartRequired'] == 'YES':
                    restartRequired += 1
                patchList.append({'swPatch': softwareUpdate['ignoreKey'], 'patchName': softwareUpdate['name'], 'sizeInKB': softwareUpdate['sizeInKB'], 'restartRequired':softwareUpdate['restartRequired'], 'productKey': softwareUpdate['productKey']})
        serviceStatus = "totalPatches:%s" % (normalPatches + recommendedPatches)
        if recommendedPatches:
            serviceStatus = "%s recommendedPatches:%s" % (serviceStatus, recommendedPatches)
        if restartRequired:
            serviceStatus = "%s restartRequired" % (serviceStatus)
        if checkService == 'softwareupdatesList':
            for swPatch in patchList:
                restartStatus = ""
                if swPatch['restartRequired'] == 'YES':
                    restartStatus = " : [RESTART]"
                print "%s : %skB : [%s]%s" % (swPatch['swPatch'], swPatch['sizeInKB'], swPatch['productKey'], restartStatus)
    else:
        serviceStatus = "ERROR:Unknown command '%s'" % (checkService)
        # Set this to empty to stop the debug code erroring out
        serviceData = {}
        unknownCount += 1
elif checkService == "serverRole":
    serviceData = getServiceData('servermgr_info', 'getState', 'withDetails')
    if 'serviceConfig' in serviceData:
        if 'roles' in serviceData['serviceConfig']:
            serviceStatus = "Server Role(s):\n"
            for serverRole, serverRoleStatus in serviceData['serviceConfig']['roles'].iteritems():
                if serverRoleStatus['configured']:
                    serviceStatus = "%s\t%s\n" % (serviceStatus, serverRole)
                else:
                    serviceStatus = "No Configured Roles\n"
        if 'services' in serviceData['serviceConfig']:
            serviceStatus = "%sActive Services:\n" % (serviceStatus)
            for serviceName, serviceNameStatus in serviceData['serviceConfig']['services'].iteritems():
                if serviceNameStatus['configured']:
                    serviceStatus = "%s\t%s\n" % (serviceStatus, serviceName)
    serviceStatus.rstrip('\n')
# TODO - XSan Testing
elif checkService == "xsan":
    # Get The initial Service State
    serviceData = getServiceData('servermgr_%s' % checkService, 'getState')
    serviceStatus, criticalCount, warningCount, unknownCount = getServiceState(serviceData)
    for xsanCheck in ['getComputerProperties', 'getLUNs', 'getHistoryInfo', 'getVolumes']:
        serviceData = getServiceData('servermgr_%s' % checkService, xsanCheck)
        serviceStatus, criticalCount, warningCount, unknownCount = getServiceState(serviceData)
    serviceData = getServiceData('servermgr_%s' % checkService, 'getQuotas', name='SANVolume')
    serviceStatus, criticalCount, warningCount, unknownCount = getServiceState(serviceData)
    sys.exit(0)
# TODO - END
elif checkService == "xserveraid":
    # From testing all Xserves don't consistantly have the profile mode
    serviceData = getServiceData('servermgr_xserve', 'profile')
    # Check Valid Data
    if "Profile" in serviceData.keys():
        raidStatus = xraidStatus(serviceData['Profile'])
        if 'Battery Info' in raidStatus.keys():
            batteryState = raidStatus['Battery Info']['State']
            if batteryState != 'Working battery':
                criticalCount += 1
            serviceStatus += "Battery:'%s' " % (batteryState)
        if 'RAID Sets' in raidStatus.keys():
            for raidSet, raidSetInfo in raidStatus['RAID Sets'].iteritems():
                raidSetState = raidSetInfo['Status']
                if raidSetState == 'Viable (In transition)':
                    warningCount += 1
                elif raidSetState != 'Viable (Good)':
                    criticalCount += 1
                serviceStatus += "RaidSet(%s):'%s' " % (raidSet, raidSetState)
        if 'Volumes' in raidStatus.keys():
            for volName, volInfo in raidStatus['Volumes'].iteritems():
                volDegraded = volInfo['Status']['Degraded']
                if volDegraded != 'No':
                    volState = 'Degraded'
                    criticalCount += 1
                else:
                    volState = "OK"
                serviceStatus += "Volume(%s):'%s' " % (volName, volState)
        if 'Drives' in raidStatus.keys():
            for driveName, driveInfo in raidStatus['Drives'].iteritems():
                driveFailed = driveInfo['Status']['Failed']
                if driveFailed != 'No':
                    driveState = 'Failed'
                    criticalCount += 1
                else:
                    driveState = "OK"
                serviceStatus += "Drive(%s):'%s' " % (driveName, driveState)
    else:
        criticalCount = 1
        serviceStatus = "ERROR:Unable To Retrieve Information"
elif checkService in osxServices:
    serviceData = getServiceData('servermgr_%s' % checkService, 'getState', 'withDetails')
    serviceStatus, criticalCount, warningCount, unknownCount = getServiceState(serviceData)
    subServices = osxServices[checkService]
    if not criticalCount:
        # Not All Services Have Sub-Services
        if len(subServices) > 0:
            subServiceStatus, warningCount = getSubServiceState(warningCount, subServices, serviceData, subServiceIgnore)
            # Some services have sub-services that are not always there
            # if they are there then return the sub-services-status instead
            if subServiceStatus not in (None, ''):
                if masterState:
                    serviceStatus = "%s %s" % (serviceStatus, subServiceStatus)
                else:
                    serviceStatus = subServiceStatus
else:
    unknownCount = 1
    serviceStatus = "ERROR:Unknown command '%s'" % (checkService)
    # Set this to empty to stop the debug code erroring out
    serviceData = {}

# If In Debug Mode Print Out The Returned Dictionary So Can Check Values etc.
if debugMode:
    serverMessage = serviceData
    logMessage(serverMessage, serverAddress, serverPort)
    serverMessage = "#### EXIT STATUS ####\nunknownCount: %s\nwarningCount: %s\ncriticalCount: %s" % (unknownCount, warningCount, criticalCount)
    logMessage(serverMessage, serverAddress, serverPort)
    serverMessage = "serviceStatus: '%s'" % serviceStatus
    logMessage(serverMessage, serverAddress, serverPort)
# End Checks Print Status Message And Exit With Correct Exit Code
# Nagios Return Codes
# 0 - OK
# 1 - Warning
# 2 - Critical
# 3 - Unknown (Invalid command line arguments were supplied to the plugin or
#              low-level failures internal to the plugin (such as unable to
#              fork, or open a tcp socket) that prevent it from performing the
#              specified operation. Higher-level errors (such as name resolution
#              errors, socket timeouts, etc) are outside of the control of
#              plugins and should generally NOT be reported as UNKNOWN states.)
if displayPerfData:
    print serviceStatus + "|" + perfData
else:
    print serviceStatus
if criticalCount:
        sys.exit(2)
elif warningCount:
    sys.exit(1)
elif unknownCount:
        sys.exit(3)
else :
        sys.exit(0)
